/*******************************************************************************
 * Copyright (c) 2000, 2007 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     Cagatay Kavukcuoglu <cagatayk@acm.org>
 *     - Fix for bug 10025 - Resizing views should not use height ratios
 *******************************************************************************/
package org.eclipse.ui.internal;

import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.ISizeProvider;
import org.eclipse.ui.IWorkbenchWindow;
//import org.eclipse.ui.internal.dnd.IDropTarget;
import org.eclipse.ui.internal.dnd.IDropTarget;
import org.eclipse.ui.internal.dnd.SwtUtil;

/**
 * A presentation part is used to build the presentation for the
 * workbench.  Common subclasses are pane and folder.
 */
abstract public class LayoutPart implements ISizeProvider {
    protected ILayoutContainer container;

    protected String id;

    public static final String PROP_VISIBILITY = "PROP_VISIBILITY"; //$NON-NLS-1$
    
    /**
     * Number of times deferUpdates(true) has been called without a corresponding
     * deferUpdates(false)
     */
	private int deferCount = 0;

    /**
     * PresentationPart constructor comment.
     */
    public LayoutPart(String id) {
        super();
        this.id = id;
    }
    
    /**
     * When a layout part closes, focus will return to a previously active part.
     * This method determines whether this part should be considered for activation
     * when another part closes. If a group of parts are all closing at the same time,
     * they will all return false from this method while closing to ensure that the
     * parent does not activate a part that is in the process of closing. Parts will
     * also return false from this method if they are minimized, closed fast views,
     * obscured by zoom, etc.
     * 
     * @return true iff the parts in this container may be given focus when the active
     * part is closed
     */
    public boolean allowsAutoFocus() {
        if (container != null) {
            return container.allowsAutoFocus();
        }
        return true;
    }


    /**
     * Creates the SWT control
     */
    abstract public void createControl(Composite parent);

    /** 
     * Disposes the SWT control
     */
    public void dispose() {
    }

    /**
     * Gets the presentation bounds.
     */
    public Rectangle getBounds() {
        return new Rectangle(0, 0, 0, 0);
    }

    /**
     * Gets the parent for this part.
     * <p>
     * In general, this is non-null if the object has been added to a container and the
     * container's widgetry exists. The exception to this rule is PartPlaceholders
     * created when restoring a ViewStack using restoreState, which point to the 
     * ViewStack even if its widgetry doesn't exist yet. Returns null in the remaining
     * cases.
     * </p> 
     * <p>
     * TODO: change the semantics of this method to always point to the parent container,
     * regardless of whether its widgetry exists. Locate and refactor code that is currently 
     * depending on the special cases.
     * </p>
     */
    public ILayoutContainer getContainer() {
        return container;
    }

    /**
     * Get the part control.  This method may return null.
     */
    abstract public Control getControl();

    /**
     * Gets the ID for this part.
     */
    public String getID() {
        return id;
    }

    /**
     * Returns the compound ID for this part.
     * The compound ID is of the form: primaryId [':' + secondaryId]
     * 
     * @return the compound ID for this part.
     */
    public String getCompoundId() {
        return getID();
    }

    public boolean isCompressible() {
        return false;
    }

    /**
     * Gets the presentation size.
     */
    public Point getSize() {
        Rectangle r = getBounds();
        Point ptSize = new Point(r.width, r.height);
        return ptSize;
    }

    /**
     * @see org.eclipse.ui.presentations.StackPresentation#getSizeFlags(boolean)
     * 
     */
    public int getSizeFlags(boolean horizontal) {
        return SWT.MIN;
    }
    
    /**
     * @see org.eclipse.ui.presentations.StackPresentation#computePreferredSize(boolean, int, int, int)
     * 
     */
    public int computePreferredSize(boolean width, int availableParallel, int availablePerpendicular, int preferredParallel) {
    	
    	return preferredParallel;    	
    }

    public IDropTarget getDropTarget(Object draggedObject, Point displayCoordinates) {
        return null;
    }
    
    public boolean isDocked() {
        Shell s = getShell();
        if (s == null) {
            return false;
        }
        
        return s.getData() instanceof IWorkbenchWindow;
    }
    
    public Shell getShell() {
        Control ctrl = getControl();
        if (!SwtUtil.isDisposed(ctrl)) {
            return ctrl.getShell();
        }
        return null;        
    }

    /**
	 * Returns the workbench window window for a part.
	 * 
	 * @return the workbench window, or <code>null</code> if there's no window
	 *         associated with this part.
	 */
    public IWorkbenchWindow getWorkbenchWindow() {
        Shell s = getShell();
        if (s==null) {
        	return null;
        }
        Object data = s.getData();
        if (data instanceof IWorkbenchWindow) {
            return (IWorkbenchWindow)data;
        } 
// RAP [rh] DetachedWindow not supported
//        else if (data instanceof DetachedWindow) {
//            return ((DetachedWindow) data).getWorkbenchPage()
//                .getWorkbenchWindow();
//        }
        
        return null;
        
    }

    /**
     * Move the control over another one.
     */
    public void moveAbove(Control refControl) {
    }

    /**
     * Reparent a part.
     */
    public void reparent(Composite newParent) {
        Control control = getControl();
        if ((control == null) || (control.getParent() == newParent)) {
            return;
        }

        if (control.isReparentable()) {
            // make control small in case it is not resized with other controls
            //control.setBounds(0, 0, 0, 0);
            // By setting the control to disabled before moving it,
            // we ensure that the focus goes away from the control and its children
            // and moves somewhere else
            boolean enabled = control.getEnabled();
            control.setEnabled(false);
            control.setParent(newParent);
            control.setEnabled(enabled);
            control.moveAbove(null);
        }
    }

    /**
     * Returns true if this part was set visible. This returns whatever was last passed into
     * setVisible, but does not necessarily indicate that the part can be seen (ie: one of its
     * ancestors may be invisible) 
     */
    public boolean getVisible() {
        Control ctrl = getControl();
        if (!SwtUtil.isDisposed(ctrl)) {
			return ctrl.getVisible();
		}
        return false;    
    }
    
    /**
     * Returns true if this part can be seen. Returns false if the part or any of its ancestors
     * are invisible.
     */
    public boolean isVisible() {
        Control ctrl = getControl();
        if (ctrl != null && !ctrl.isDisposed()) {
			return ctrl.isVisible();
		}
        return false;
    }

    /**
     * Shows the receiver if <code>visible</code> is true otherwise hide it.
     */
    public void setVisible(boolean makeVisible) {
        Control ctrl = getControl();
        if (!SwtUtil.isDisposed(ctrl)) {
            if (makeVisible == ctrl.getVisible()) {
				return;
			}

            if (!makeVisible && isFocusAncestor(ctrl)) {
                // Workaround for Bug 60970 [EditorMgmt] setActive() called on an editor when it does not have focus.
                // Force focus on the shell so that when ctrl is hidden,
                // SWT does not try to send focus elsewhere, which may cause
                // some other part to be activated, which affects the part
                // activation order and can cause flicker.
                ctrl.getShell().forceFocus();
            }

            ctrl.setVisible(makeVisible);
        }
    }

    /**
     * Returns <code>true</code> if the given control or any of its descendents has focus.
     */
    private boolean isFocusAncestor(Control ctrl) {
        Control f = ctrl.getDisplay().getFocusControl();
        while (f != null && f != ctrl) {
            f = f.getParent();
        }
        return f == ctrl;
    }

    /**
     * Sets the presentation bounds.
     */
    public void setBounds(Rectangle r) {
        Control ctrl = getControl();
        if (!SwtUtil.isDisposed(ctrl)) {
			ctrl.setBounds(r);
		}
    }

    /**
     * Sets the parent for this part.
     */
    public void setContainer(ILayoutContainer container) {
        
        this.container = container;
        
        if (container != null) {
            setZoomed(container.childIsZoomed(this));
        }
    }

    /**
     * Sets focus to this part.
     */
    public void setFocus() {
    }

    /** 
     * Sets the part ID.
     */
    public void setID(String str) {
        id = str;
    }

    /* (non-Javadoc)
     * @see org.eclipse.ui.internal.IWorkbenchDragDropPart#getPart()
     */
    public LayoutPart getPart() {
        return this;
    }

    public void childRequestZoomIn(LayoutPart toZoom) {
        
    }
    
    public void childRequestZoomOut() {
        
    }
    
    public final void requestZoomOut() {
        ILayoutContainer container = getContainer();
        if (container != null) {
            container.childRequestZoomOut();
        }
    }
    
    public final void requestZoomIn() {
        ILayoutContainer container = getContainer();
        if (container != null) {
            container.childRequestZoomIn(this);
        }
    }
    
    public final boolean isObscuredByZoom() {
        ILayoutContainer container = getContainer();
        
        if (container != null) {
            return container.childObscuredByZoom(this);
        }
        
        return false;
    }
    
    public boolean childObscuredByZoom(LayoutPart toTest) {
        return false;
    }
    
    public boolean childIsZoomed(LayoutPart childToTest) {
        return false;
    }
    
    public void setZoomed(boolean isZoomed) {

    }
    
    /**
     * deferUpdates(true) disables widget updates until a corresponding call to
     * deferUpdates(false). Exactly what gets deferred is the decision
     * of each LayoutPart, however the part may only defer operations in a manner
     * that does not affect the final result. 
     * That is, the state of the receiver after the final call to deferUpdates(false)
     * must be exactly the same as it would have been if nothing had been deferred. 
     * 
     * @param shouldDefer true iff events should be deferred 
     */
    public final void deferUpdates(boolean shouldDefer) {
    	if (shouldDefer) {
    		if (deferCount == 0) {
    			startDeferringEvents();
    		}
    		deferCount++;
    	} else {
    		if (deferCount > 0) {
    			deferCount--;
    			if (deferCount == 0) {
    				handleDeferredEvents();
    			}
    		}
    	}
    }
    
    /**
     * This is called when deferUpdates(true) causes UI events for this
     * part to be deferred. Subclasses can overload to initialize any data
     * structures that they will use to collect deferred events.
     */
    protected void startDeferringEvents() {
    	
    }
    
    /**
     * Immediately processes all UI events which were deferred due to a call to
     * deferUpdates(true). This is called when the last call is made to 
     * deferUpdates(false). Subclasses should overload this method if they
     * defer some or all UI processing during deferUpdates.
     */
    protected void handleDeferredEvents() {
    	
    }
    
    /**
     * Subclasses can call this method to determine whether UI updates should
     * be deferred. Returns true iff there have been any calls to deferUpdates(true)
     * without a corresponding call to deferUpdates(false). Any operation which is
     * deferred based on the result of this method should be performed later within
     * handleDeferredEvents(). 
     * 
     * @return true iff updates should be deferred.
     */
    protected final boolean isDeferred() {
    	return deferCount > 0;
    }

    /**
     * Writes a description of the layout to the given string buffer.
     * This is used for drag-drop test suites to determine if two layouts are the
     * same. Like a hash code, the description should compare as equal iff the
     * layouts are the same. However, it should be user-readable in order to
     * help debug failed tests. Although these are english readable strings,
     * they do not need to be translated.
     * 
     * @param buf
     */
    public void describeLayout(StringBuffer buf) {

    }

    /**
     * Returns an id representing this part, suitable for use in a placeholder.
     * 
     */
    public String getPlaceHolderId() {
        return getID();
    }

    public void resizeChild(LayoutPart childThatChanged) {

    }

    public void flushLayout() {
        ILayoutContainer container = getContainer();
        if (getContainer() != null) {
            container.resizeChild(this);
        }
    }

    /**
     * Returns true iff the given part can be added to this ILayoutContainer
     * @param toAdd
     * @return
     */
    public boolean allowsAdd(LayoutPart toAdd) {
        return false;
    }
    
    /**
     * Tests the integrity of this object. Throws an exception if the object's state
     * is not internally consistent. For use in test suites.
     */
    public void testInvariants() {
    }
}
